#!/usr/bin/env ruby

# -------------------------------------------------------------------------- #
# Copyright 2002-2023, OpenNebula Project, OpenNebula Systems                #
#                                                                            #
# Licensed under the Apache License, Version 2.0 (the "License"); you may    #
# not use this file except in compliance with the License. You may obtain    #
# a copy of the License at                                                   #
#                                                                            #
# http://www.apache.org/licenses/LICENSE-2.0                                 #
#                                                                            #
# Unless required by applicable law or agreed to in writing, software        #
# distributed under the License is distributed on an "AS IS" BASIS,          #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #
# See the License for the specific language governing permissions and        #
# limitations under the License.                                             #
#--------------------------------------------------------------------------- #

ONE_LOCATION = ENV['ONE_LOCATION']

if !ONE_LOCATION
    RUBY_LIB_LOCATION = '/usr/lib/one/ruby'
    GEMS_LOCATION     = '/usr/share/one/gems'
    REMOTES_LOCATION  = '/var/lib/one/remotes/'
else
    RUBY_LIB_LOCATION = ONE_LOCATION + '/lib/ruby'
    GEMS_LOCATION     = ONE_LOCATION + '/share/gems'
    REMOTES_LOCATION  = ONE_LOCATION + '/var/remotes/'
end

# %%RUBYGEMS_SETUP_BEGIN%%
if File.directory?(GEMS_LOCATION)
    real_gems_path = File.realpath(GEMS_LOCATION)
    if !defined?(Gem) || Gem.path != [real_gems_path]
        $LOAD_PATH.reject! {|l| l =~ /vendor_ruby/ }

        # Suppress warnings from Rubygems
        # https://github.com/OpenNebula/one/issues/5379
        begin
            verb = $VERBOSE
            $VERBOSE = nil
            require 'rubygems'
            Gem.use_paths(real_gems_path)
        ensure
            $VERBOSE = verb
        end
    end
end
# %%RUBYGEMS_SETUP_END%%

$LOAD_PATH << RUBY_LIB_LOCATION
$LOAD_PATH << RUBY_LIB_LOCATION + '/cli'
$LOAD_PATH << REMOTES_LOCATION + 'vmm/vcenter/'

require 'command_parser'
require 'one_helper/onevcenter_helper'
require 'vcenter_driver'

CommandParser::CmdParser.new(ARGV) do
    usage '`onevcenter` <command> [<args>] [<options>]'
    version OpenNebulaHelper::ONE_VERSION

    helper = OneVcenterHelper.new

    before_proc do
        helper.set_client(options)
    end

    ############################################################################
    # Authentication Options
    ############################################################################

    VCENTER = {
        :name  => 'vcenter',
        :large => '--vcenter vCenter',
        :description => 'The vCenter hostname',
        :format => String
    }

    USER = {
        :name  => 'vuser',
        :large => '--vuser username',
        :description => 'The username to interact with vCenter',
        :format => String
    }

    PASS = {
        :name  => 'vpass',
        :large => '--vpass password',
        :description => 'The password for the user',
        :format => String
    }

    PORT = {
        :name  => 'port',
        :short => '-p port',
        :large => '--port port',
        :format => String,
        :description => 'vCenter API port, defaults to 443 (SSL) or 80'
    }

    AUTH_OPTS = [VCENTER, USER, PASS, PORT]

    ############################################################################
    # List & Import
    ############################################################################

    OBJECT = {
        :name  => 'object',
        :short => '-o object',
        :large => '--object object ',
        :format => String,
        :description => 'vCenter object: [datastores, templates,'\
                        'networks, datastores, images, hosts]'
    }

    HOST = {
        :name  => 'host',
        :short => '-h host_id',
        :large => '--host host_id',
        :format => String,
        :description => 'OpenNebula host used to authenticate the operation'
    }

    DATASTORE = {
        :name  => 'datastore',
        :short => '-d datastore_id',
        :large => '--datastore datastore_id',
        :format => String,
        :description => 'OpenNebula datastore used'
    }

    CONFIG = {
        :name  => 'configuration',
        :large => '--config file',
        :format => String,
        :description => 'Configuration file for custom options'
    }

    LINKED_CLONE = {
        :name  => 'linked_clone',
        :large => '--linked_clone linked_clone',
        :format => String,
        :description => 'Import template as linked clone, 0/1'
    }

    COPY = {
        :name  => 'copy',
        :large => '--copy copy',
        :format => String,
        :description => 'Import template as copy, 0/1'
    }

    NAME = {
        :name  => 'name',
        :large => '--name name',
        :format => String,
        :description => 'Import template copy with name'
    }

    FOLDER = {
        :name  => 'folder',
        :large => '--folder folder',
        :format => String,
        :description => 'Import template in folder'
    }

    USE_DEFAULTS = {
        :name  => 'defaults',
        :large => '--use-defaults',
        :description => 'Use defaults for answers to questions',
        :format => String
    }

    ALL = {
        :name  => 'all',
        :large => '--all',
        :description => 'Import all list',
        :format => String
    }

    CLUSTER_REF = {
        :name  => 'cluster_ref',
        :large => '--cluster-ref cluster_ref',
        :format => String,
        :description => 'Cluster ref to import'
    }

    LIST_OPTS = [OBJECT, HOST, DATASTORE, CLIHelper::CSV_OPT] + AUTH_OPTS

    ############################################################################
    # Global Options
    ############################################################################
    cmd_options = CommandParser::OPTIONS - [CommandParser::VERBOSE]
    set :option, cmd_options + OpenNebulaHelper::CLIENT_OPTIONS

    ############################################################################
    # list resources
    ############################################################################
    list_desc = <<-EOT.unindent
        Show a list with unimported vCenter objects

        Examples:
           - listing available templates:

             onevcenter list -o templates -h <host_id>

           - listing available images:

             onevcenter list -o datastores -h <host_id> -d <ds-img_id>

           - listing available clusters:

             onevcenter list -o hosts --vcenter <IP> --vuser <U> --vpass <P>
    EOT

    command :list, list_desc, :options => LIST_OPTS do
        begin
            args = helper.parse_opts(options)

            args[:filter] = true
            args[:short]  = true

            helper.list(options, args)
        rescue StandardError => e
            STDERR.puts e.message
            exit 1
        end

        0
    end

    list_desc = <<-EOT.unindent
        Show a list with unimported vCenter objects excluding all filters

        Examples:
          - listing networks including uplinks:

            onevcenter list_all -o networks -h <host_id>
    EOT

    command :list_all, list_desc, :options => LIST_OPTS do
        begin
            args = helper.parse_opts(options)

            args[:filter] = false
            args[:short]  = true

            helper.list(options, args)
        rescue StandardError => e
            STDERR.puts e.message
            exit 1
        end

        0
    end

    import_desc = <<-EOT.unindent
        Import the the desired vCenter object

        Examples:
           - importing first datastore

             onevcenter import -o templates -h <host_id>

           - importing 2 concrete templates:

             onevcenter import "vm-1252, vm-553, vm-1248" -o templates -h <host_id>

           - importing a image range:

             onevcenter import 0..10 -o images -h <host_id> -d <ds-img_id>
    EOT

    command :import,
            import_desc,
            [:oid, nil],
            :options => [OBJECT, HOST, DATASTORE] do
        begin
            vi_client = VCenterDriver::VIClient.new_from_host(options[:host])
            importer  = VCenterDriver::VcImporter.new_child(helper.client,
                                                            vi_client,
                                                            options[:object])

            importer.retrieve_resources(helper.parse_opts(options))
            indexes = importer.get_indexes(args.first)

            if indexes.nil?
                raise "Could not get any unimported #{options[:object]}"\
                      " resources info in host: #{options[:host]} with"\
                      " this input: #{args.first}"
            end

            importer.process_import(indexes, options) do |object_info|
                helper.cli_dialogue(object_info)
            end

            importer.stdout
        rescue StandardError => e
            STDERR.puts e.message
            exit 1
        end

        0
    end

    command :import_defaults,
            import_desc,
            [:oid, nil],
            :options => [OBJECT,
                         HOST,
                         DATASTORE,
                         CONFIG,
                         FOLDER,
                         LINKED_CLONE,
                         COPY,
                         NAME] do
        begin
            vi_client = VCenterDriver::VIClient.new_from_host(options[:host])
            importer  = VCenterDriver::VcImporter.new_child(helper.client,
                                                            vi_client,
                                                            options[:object])

            if options[:object] == 'networks' && !args.first.nil?
                indexes = args.first
            else
                importer.retrieve_resources(helper.parse_opts(options))
                indexes = importer.get_indexes(args.first)
            end

            if options[:object] == 'templates' &&
               indexes &&
               indexes.split(',').length == 1
                opts = {
                    :type         => 'default',
                    :linked_clone => '0',
                    :copy         => '0',
                    :name         => '',
                    :folder       => ''
                }

                if options[:linked_clone]
                    opts[:linked_clone] = options[:linked_clone]
                end

                opts[:copy]   = options[:copy] if options[:copy]
                opts[:name]   = options[:name] if options[:name]
                opts[:folder] = options[:folder] if options[:folder]

                options[indexes] = opts
            end

            importer.process_import(indexes, options)
            importer.stdout
        rescue StandardError => e
            STDERR.puts e.message
            exit 1
        end

        0
    end

    ############################################################################
    # Import clusters
    ############################################################################
    host_desc = <<-EOT.unindent
        Import vCenter clusters as OpenNebula hosts

        Example:
           - Get available clusters:

             onevcenter hosts --vcenter <vcenter> --vuser <user> --vpass <pass>
    EOT

    command :hosts,
            host_desc,
            :options => [CLUSTER_REF, USE_DEFAULTS] + AUTH_OPTS do
        con_ops = helper.connection_options('Hosts', options)

        begin
            VCenterDriver::VcImporter.import_clusters(con_ops, options)
        rescue StandardError
            exit 1
        end

        0
    end

    ############################################################################
    # Clear VM tags
    ############################################################################
    cleartags_desc = <<-EOT.unindent
        Clear extraconfig tags from a vCenter VM, useful when a VM has been
        launched by OpenNebula and needs to be reimported

        Example:
           - Clean VM 15:

           onevcenter cleargs 15
    EOT

    command :cleartags, cleartags_desc, :vmid do
        vmid       = args[0]
        remove_str = "\n        onevm recover --delete-db #{vmid}" \
                     "\n\nAfter a monitoring cycle, the VM will appear "\
                     'as a Wild VM for reimport.'

        print 'Extracting information from VM ' + vmid

        begin
            print '.'

            client = Client.new

            vm_pool   = VirtualMachinePool.new(client, -1)
            host_pool = HostPool.new(client)
            deploy_id = -1
            host_id   = -1
            hostname  = ''

            rc = vm_pool.info
            raise rc.message if OpenNebula.is_error?(rc)

            rc = host_pool.info
            raise rc.message if OpenNebula.is_error?(rc)

            vm_pool.each do |vm|
                next if vm.id.to_s != vmid

                deploy_id  = vm.deploy_id
                vm_history = vm.to_hash['VM']['HISTORY_RECORDS']['HISTORY']
                hostname   = vm_history['HOSTNAME']
                break
            end

            host_pool.each do |host|
                if host.name == hostname
                    host_id = host.id
                end
            end

            vi_client = VCenterDriver::VIClient.new_from_host(host_id)
            vm        = VCenterDriver::VirtualMachine
                        .new(vi_client, deploy_id, vmid)

            keys = vm.extra_config_keys

            print '.'

            if keys.empty?
                puts "\n\nNo OpenNebula keys present, is safe to remove the VM."
                puts remove_str
                exit 0
            end

            puts '.'
            puts 'The following keys will be removed:'
            keys.each {|key| puts "\t- #{key}" }

            vm.clear_tags
        rescue StandardError => e
            STDERR.puts "Couldn't clear VM tags. Reason: #{e.message}"
            exit 1
        end

        puts "\nKeys removed from VM. Is safe to remove it"
        puts remove_str

        0
    end
end
